package com.bluesky.plum.richdomain;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.beanutils.PropertyUtils;

import com.bluesky.plum.domain.Person;
import com.bluesky.plum.util.IndexedMap;

/**
 * it is a collection of rich domain fields. it support conversion between POJO
 * fields and Method parameters.
 * 
 * 
 * @author HZ00260
 * 
 */
public class RichDomainFields {

	private static Map<Class, RichDomainFields> objectMap = new HashMap<Class, RichDomainFields>();
	private static Map<RichDomainMethod, RichDomainFields> methodMap = new HashMap<RichDomainMethod, RichDomainFields>();

	public static RichDomainFields createFromMethod(RichDomainMethod rdm) {
		if (methodMap.containsKey(rdm)) {
			System.out.println("hit method cache:" + rdm);
			return methodMap.get(rdm);
		} else {

			RichDomainFields rslt = new RichDomainFields();
			rslt.generateRichFieldsForMethod(rdm.method, rdm.genericTypeMap);

			methodMap.put(rdm, rslt);
			return rslt;
		}
	}

	public static RichDomainFields createFromEntity(Object obj) {
		if (obj == null)
			throw new RuntimeException("object should not be null");

		if (objectMap.containsKey(obj.getClass())) {
			System.out.println("hit object cache:" + obj.getClass());
			RichDomainFields rdfs1 = objectMap.get(obj.getClass());
			RichDomainFields rdfs2 = new RichDomainFields();
			rdfs2.copy(rdfs1);

			// update the field.value
			for (RichDomainField rdf : rdfs2.fields.getList()) {
				Object v = null;
				try {
					v = PropertyUtils.getProperty(obj, rdf.name);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				rdf.value = v;
			}

			return rdfs2;

		} else {

			RichDomainFields rslt = new RichDomainFields();
			rslt.generateRichFieldsForPojo(obj);

			objectMap.put(obj.getClass(), rslt);
			return rslt;
		}

	}

	private IndexedMap<String, RichDomainField> fields = new IndexedMap<String, RichDomainField>();
	private RichDomainField returnField;

	public IndexedMap<String, RichDomainField> getFields() {
		return fields;
	}

	public void setFields(IndexedMap<String, RichDomainField> fields) {
		this.fields = fields;
	}

	private RichDomainFields() {

	}

	/**
	 * read info from annotation or XML, write to rich domain field
	 * 
	 * @param target
	 */
	protected void generateRichFieldsForPojo(Object target) {
		// iterate every field of the target and create the ui component
		PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(target);

		for (PropertyDescriptor pd : pds) {
			
			Field f;
			try {
				// System.out.println("pd:" + pd.getName());
				f = target.getClass().getDeclaredField(pd.getName());
			} catch (SecurityException e1) {

				e1.printStackTrace();
				throw new RuntimeException(e1);
			} catch (NoSuchFieldException e) {
				System.out.println("no such field:" + pd.getName());
				// it is ok, next one
				continue;
			}

			System.out.println("generateRichFieldsForPojo() type/name:" + pd.getPropertyType()+"/"+pd.getName());
			
			FieldType fieldType = FieldType.getType(pd.getPropertyType());
			Object fieldValue = null;

			try {
				fieldValue = PropertyUtils.getProperty(target, pd.getName());
			} catch (Exception e) {

				e.printStackTrace();
				throw new RuntimeException(e);
			}

			RichDomainField rdf = fieldType.createRichDomainField();

			rdf.value = fieldValue;
			rdf.name = pd.getName();

			rdf.caption = rdf.name;

			rdf.enabled = true;// set default value

			rdf.position = 9999;

			fieldType.readFieldAnnotation(f, rdf);

			rdf.fieldClass = f.getType();
			if (FieldType.getType(rdf.fieldClass) == FieldType.Collection) {
				Type genericFieldType = f.getGenericType();

				if (genericFieldType instanceof ParameterizedType) {
					ParameterizedType aType = (ParameterizedType) genericFieldType;
					Type[] fieldArgTypes = aType.getActualTypeArguments();
					if (fieldArgTypes.length != 1)
						throw new RuntimeException(
								"size of field Arg Types is not 1.(it supposed to be a List<T>)");
					try {
						if (fieldArgTypes[0].toString().startsWith("class "))

						{
							// class com.abc.project.app.Foo
							String className = fieldArgTypes[0].toString()
									.substring(6);
							rdf.elementClass = Class.forName(className);
						}
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						throw new RuntimeException(e);
					}
				}

			}

			rdf.fieldType = fieldType;

			if (rdf.enabled) {
				// System.out.println("put field:" + rdf.name);
				this.fields.put(rdf.name, rdf);
			}
		}

		sortFieldList();
	}
	
	
	/**
	 * read info from annotation or XML, write to rich domain field
	 * V2 , read from public properties.
	 * @param target
	 */
	protected void generateRichFieldsForPojoV2(Object target) {
		// iterate every field of the target and create the ui component
		PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(target);

		target.getClass().getDeclaredFields();
		for (PropertyDescriptor pd : pds) {
			
			Field f;
			try {
				// System.out.println("pd:" + pd.getName());
				f = target.getClass().getDeclaredField(pd.getName());
			} catch (SecurityException e1) {

				e1.printStackTrace();
				throw new RuntimeException(e1);
			} catch (NoSuchFieldException e) {
				System.out.println("no such field:" + pd.getName());
				// it is ok, next one
				continue;
			}

			System.out.println("generateRichFieldsForPojo() type/name:" + pd.getPropertyType()+"/"+pd.getName());
			
			FieldType fieldType = FieldType.getType(pd.getPropertyType());
			Object fieldValue = null;

			try {
				fieldValue = PropertyUtils.getProperty(target, pd.getName());
			} catch (Exception e) {

				e.printStackTrace();
				throw new RuntimeException(e);
			}

			RichDomainField rdf = fieldType.createRichDomainField();

			rdf.value = fieldValue;
			rdf.name = pd.getName();

			rdf.caption = rdf.name;

			rdf.enabled = true;// set default value

			rdf.position = 9999;

			fieldType.readFieldAnnotation(f, rdf);

			rdf.fieldClass = f.getType();
			if (FieldType.getType(rdf.fieldClass) == FieldType.Collection) {
				Type genericFieldType = f.getGenericType();

				if (genericFieldType instanceof ParameterizedType) {
					ParameterizedType aType = (ParameterizedType) genericFieldType;
					Type[] fieldArgTypes = aType.getActualTypeArguments();
					if (fieldArgTypes.length != 1)
						throw new RuntimeException(
								"size of field Arg Types is not 1.(it supposed to be a List<T>)");
					try {
						if (fieldArgTypes[0].toString().startsWith("class "))

						{
							// class com.abc.project.app.Foo
							String className = fieldArgTypes[0].toString()
									.substring(6);
							rdf.elementClass = Class.forName(className);
						}
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						throw new RuntimeException(e);
					}
				}

			}

			rdf.fieldType = fieldType;

			if (rdf.enabled) {
				// System.out.println("put field:" + rdf.name);
				this.fields.put(rdf.name, rdf);
			}
		}

		sortFieldList();
	}

	protected void generateRichFieldsForMethod(Method method,
			Map<String, Class> genericTypeMap) {
		// iterate every field of the target and create the ui component
		Class[] paramTypes = method.getParameterTypes();
		Type[] genericTypes = method.getGenericParameterTypes();

		Annotation[][] annotationArray = method.getParameterAnnotations();

		for (int i = 0; i < paramTypes.length; i++) {
			Class parameterClass;
			// check if it is Generic Typed, replace with its real type.
			if (genericTypes[i] instanceof TypeVariable) {
				TypeVariable tv = (TypeVariable) genericTypes[i];
				parameterClass = genericTypeMap.get(tv.getName());
			} else
				parameterClass = paramTypes[i];

			FieldType fieldType = FieldType.getType(parameterClass);

			// put anooation array into a map
			Map<Class, Annotation> paramAnnotationMap = new HashMap<Class, Annotation>();
			for (Annotation anno : annotationArray[i]) {

				Class<? extends Annotation> c = anno.annotationType();
				paramAnnotationMap.put(c, anno);
			}

			RichDomainField rdf = fieldType.createRichDomainField();

			fieldType.readParameterAnnotations(paramAnnotationMap, rdf);

			rdf.fieldClass = parameterClass;

			rdf.fieldType = fieldType;

			this.fields.put(rdf.name, rdf);

			// System.out.println("put rdf:"+rdf.name);
		}

		// sortFieldList(); do not need to sort

		// return value
		returnField = new RichDomainField();
		returnField.fieldType = FieldType.getType(method.getReturnType());
		returnField.name = "result";

	}

	/*
	 * copy field values back to POJO proerties
	 */
	public void generatePojo(Object obj) {
		for (RichDomainField rdf : this.fields.getList()) {
			try {
				Field f = obj.getClass().getDeclaredField(rdf.name);
				PropertyUtils.setProperty(obj, rdf.name, rdf.value);
				// f.set(obj, rdf.value);

			} catch (Exception e) {

				e.printStackTrace();
				throw new RuntimeException(e);
			}

		}
	}

	public void print() {
		for (RichDomainField f : fields.getList()) {
			System.out.println(f);
		}
	}

	public RichDomainField getReturnField() {
		return returnField;
	}

	public void setReturnField(RichDomainField returnField) {
		this.returnField = returnField;
	}

	public void copy(RichDomainFields src) {
		// clear
		fields = new IndexedMap<String, RichDomainField>();

		for (RichDomainField rdf : src.getFields().getList()) {
			// System.out.println("copy:"+rdf.name);
			fields.put(rdf.name, rdf);
		}

	}

	private void sortFieldList() {
		int size1 = fields.getList().size();
		Collections.sort(fields.getList());
		int size2 = fields.getList().size();
		if (size1 != size2)
			throw new RuntimeException(
					"fail to sort, check field name duplication.");
	}

	public static void main(String[] args) {
		Person p = Person.getSample();
		RichDomainFields rdo = RichDomainFields.createFromEntity(p);

		rdo.print();
	}
}
